<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="u8kAc" id="u8kAc"><span data-lake-id="u36cc50bb" id="u36cc50bb">典型回答</span></h1>
  <p data-lake-id="u4d9f9ffa" id="u4d9f9ffa"><br></p>
  <p data-lake-id="u7a924b6f" id="u7a924b6f"><span data-lake-id="uc6cd7891" id="uc6cd7891">在业务中，我们通常需要把数据库中的数据变更同步到ES中，那么如何保证数据库和ES的一致性呢？通常有以下几种做法：</span></p>
  <p data-lake-id="u8a12256a" id="u8a12256a"><span data-lake-id="u7ef5ea67" id="u7ef5ea67">​</span><br></p>
  <h4 data-lake-id="oWmkP" id="oWmkP"><span data-lake-id="u785b6765" id="u785b6765">双写</span></h4>
  <p data-lake-id="u26ec32b3" id="u26ec32b3"><br></p>
  <p data-lake-id="uc4937daa" id="uc4937daa"><span data-lake-id="ud7025022" id="ud7025022">在代码中，对数据库和ES进行双写，并且先操作本地数据库，后操作ES，而且还需要把两个操作放到一个事务中：</span></p>
  <p data-lake-id="u849e4887" id="u849e4887"><span data-lake-id="u43e5e670" id="u43e5e670">​</span><br></p>
  <pre lang="java"><code>
@Transactional(rollbackFor = Exception.class)
public void update(OrderDTO orderDTO) {
	//更新本地数据库
	updateDb(orderDTO);
  //远程更新ES
	updateEs(orderDTO);
}
</code></pre>
  <p data-lake-id="u4f795c27" id="u4f795c27"><br></p>
  <p data-lake-id="u5994374a" id="u5994374a"><span data-lake-id="u91a8533c" id="u91a8533c">在以上逻辑中，如果写数据库成功，写ES失败，那么事务会回滚。</span></p>
  <p data-lake-id="u6d3efd18" id="u6d3efd18"><span data-lake-id="ud5bc0e66" id="ud5bc0e66">​</span><br></p>
  <p data-lake-id="u41663823" id="u41663823"><span data-lake-id="ub8f6251f" id="ub8f6251f">如果写数据库成功，写ES超时，实际上ES操作成功，这时候数据库会回滚，导致数据不一致。这时候需要重试来保证最终一致性。</span></p>
  <p data-lake-id="u0949b237" id="u0949b237"><span data-lake-id="ue72e9b9e" id="ue72e9b9e">​</span><br></p>
  <p data-lake-id="u91e18d04" id="u91e18d04"><span data-lake-id="uaf935423" id="uaf935423">这个方案的好处就是简单，容易实现。并且实时性比较高。</span></p>
  <p data-lake-id="u8902ce6a" id="u8902ce6a"><span data-lake-id="u7a82d113" id="u7a82d113">​</span><br></p>
  <p data-lake-id="ubbb8cdfe" id="ubbb8cdfe"><span data-lake-id="u1a549a41" id="u1a549a41">缺点首先是需要改代码，有侵入性，还有就是存在不一致的情况。并且在本地事务中发生了外调（外部调用，调ES），大大拖长了事务，白白占用数据库链接，影响整体的吞吐量。</span></p>
  <p data-lake-id="u2c746f52" id="u2c746f52"><span data-lake-id="u9af11829" id="u9af11829">​</span><br></p>
  <h4 data-lake-id="nuDbt" id="nuDbt"><span data-lake-id="u03fd8574" id="u03fd8574">MQ异步消费</span></h4>
  <p data-lake-id="u60b1b86b" id="u60b1b86b"><br></p>
  <p data-lake-id="u0abe36dd" id="u0abe36dd"><span data-lake-id="u0027e25c" id="u0027e25c">在应用中，如果我要更新数据库了，那么就抛一个消息出去，然后数据库和ES各自有一个监听者，监听消息之后各自去做数据变更，如果失败了就基于消息的重试在重新执行。</span></p>
  <p data-lake-id="ua565649c" id="ua565649c"><span data-lake-id="u7136a738" id="u7136a738">​</span><br></p>
  <p data-lake-id="u029e466f" id="u029e466f"><span data-lake-id="u6884c41b" id="u6884c41b">或者像之前那个方案一样，先操作数据库，然后异步通知ES去更新，这时候就可以借助本地消息表的方式来保证最终一致性了。</span></p>
  <p data-lake-id="ub07a868c" id="ub07a868c"><span data-lake-id="u76e1c5e0" id="u76e1c5e0">​</span><br></p>
  <p data-lake-id="u8af4dbd5" id="u8af4dbd5"><span data-lake-id="ue977d334" id="ue977d334">这个方案的好处是用了MQ，起到了解耦的作用，而且还做到了异步，提升了整体性能。</span></p>
  <p data-lake-id="ue971915e" id="ue971915e"><span data-lake-id="uc6bb2037" id="uc6bb2037">​</span><br></p>
  <p data-lake-id="u0c7ec921" id="u0c7ec921"><span data-lake-id="u824fe13e" id="u824fe13e">缺点就是MQ可能存在延迟，并且需要引入新的中间件，复杂度有所提升。</span></p>
  <p data-lake-id="u1f58cf97" id="u1f58cf97"><span data-lake-id="uaeaf6609" id="uaeaf6609">​</span><br></p>
  <p data-lake-id="u204aef4c" id="u204aef4c"><span data-lake-id="u5bf53750" id="u5bf53750">​</span><br></p>
  <h4 data-lake-id="EhQZY" id="EhQZY"><span data-lake-id="u79768165" id="u79768165">扫表定时同步</span></h4>
  <p data-lake-id="ub747ea0d" id="ub747ea0d"><br></p>
  <p data-lake-id="ue9e393aa" id="ue9e393aa"><span data-lake-id="ud2a03109" id="ud2a03109">如果是ES中的数据变更的实时性要求不高，可以考虑定时任务扫表， 然后批量更新ES。</span></p>
  <p data-lake-id="u63a07b08" id="u63a07b08"><span data-lake-id="ucd650b46" id="ucd650b46">​</span><br></p>
  <p data-lake-id="u735db479" id="u735db479"><span data-lake-id="u2be5bc83" id="u2be5bc83">这个方案优点是没有侵入性，数据库的写操作处不需要改代码。</span></p>
  <p data-lake-id="ue2c9821e" id="ue2c9821e"><span data-lake-id="u6e6b7c15" id="u6e6b7c15">​</span><br></p>
  <p data-lake-id="u947093cb" id="u947093cb"><span data-lake-id="ucb255fda" id="ucb255fda">缺点是实时性很差，并且轮询可能存在性能问题、效率问题以及给数据库带来压力。</span></p>
  <p data-lake-id="u45465297" id="u45465297"><br></p>
  <h4 data-lake-id="pXSZh" id="pXSZh"><span data-lake-id="ud3b57981" id="ud3b57981">监听binlog同步</span></h4>
  <p data-lake-id="ub40a44be" id="ub40a44be"><br></p>
  <p data-lake-id="ub2724974" id="ub2724974"><span data-lake-id="u1b873845" id="u1b873845">还有一种方案，就是可以利用数据库变更时产生的binlog来更新ES。通过监听binlog来更新ES中的数据，也有成熟的框架可以做这样的事情</span></p>
  <p data-lake-id="ued833436" id="ued833436"><span data-lake-id="u004c1785" id="u004c1785">​</span><br></p>
  <p data-lake-id="ud54a9099" id="ud54a9099"><span data-lake-id="u811a6a39" id="u811a6a39">好处就是对业务代码完全没有侵入性，业务也非常解耦，不需要关心这个ES的更新操作。</span></p>
  <p data-lake-id="u5a2d74c5" id="u5a2d74c5"><span data-lake-id="u203c7027" id="u203c7027">​</span><br></p>
  <p data-lake-id="u0c76cabf" id="u0c76cabf"><span data-lake-id="u108bf3e0" id="u108bf3e0">缺点就是需要基于binlog监听，需要引入第三方框架。存在一定的延迟。</span></p>
  <p data-lake-id="ua47f1431" id="ua47f1431"><span data-lake-id="u5c78ff94" id="u5c78ff94">​</span><br></p>
  <p data-lake-id="uf1086eb6" id="uf1086eb6"><span data-lake-id="u16769f8d" id="u16769f8d">​</span><br></p>
  <p data-lake-id="ud6676664" id="ud6676664"><strong><span data-lake-id="u90ef669a" id="u90ef669a">总结一下，目前业内比较流行的方案是基于binlog监听的这种，首先一般业务量小的业务也不太需要用ES，所以用了ES的团队，一般并不太会关心引入新框架的复杂度问题，而且ES这种搜索，一般来说，毫秒级的延迟都是可以接受的，所以，综合来讲，基于canal做数据同步的方案，是比较合适的。</span></strong></p>
 </body>
</html>